#include "DSTHSocketOps.h"
#include <stdio.h> // for snprintf
#include <errno.h>
#include <unistd.h> 
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/uio.h> // for readv
#include <memory.h> // for memset
#include "Log.h"

namespace {
typedef struct sockaddr SA;

const SA* sockaddr_cast(const struct sockaddr_in* addr)
{
  return static_cast<const SA*>((const void*)(addr));
}

SA* sockaddr_cast(struct sockaddr_in* addr)
{
  return static_cast<SA*>((void*)(addr));
}
} // namespace
namespace dsth {
namespace net {
    int CreateNonblockSocketOrDie() {
        int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (fd < 0) {            
            LOG_FATAL("create tcp socket error, errorcode:%d.", errno);
        }

        // set non-block
        int flags = ::fcntl(fd, F_GETFL, 0);
        flags |= O_NONBLOCK;
        int res = ::fcntl(fd, F_SETFL, flags);
        if (res < 0) {
            LOG_FATAL("set socket non-block error, errorcode:%d.", errno);
        }

        // set close on exec
        flags = ::fcntl(fd, F_GETFD, 0);
        flags |= FD_CLOEXEC;
        res = ::fcntl(fd, F_SETFD, flags);
        if (res < 0) {
            LOG_FATAL("set socket close on exec error, errorcode:%d.", errno);
        }
        
        return fd;
    }

    int BindOrDie(int fd, const struct sockaddr_in& addr) {
        int ret = ::bind(fd, sockaddr_cast(&addr), sizeof addr);
        if (ret < 0) {
            LOG_FATAL("bind socket failed. errorcode:%d.", errno);
        }
        return ret;
    }

    int ListenOrDie(int fd) {
        int ret = ::listen(fd, SOMAXCONN);
        if (ret < 0) {
            LOG_FATAL("listen socket error. errorcode:%d.", errno);
        }
        return ret;
    }

    void Close(int fd) {
        int ret = ::close(fd);
        if (ret < 0) {
            LOG_ERROR("close socket error. errorcode:%d.", errno);
        }
    }

    int Write(int fd, const char* data, size_t len) {
        return ::send(fd, data, len, 0);
    }

    ssize_t Readv(int fd, iovec* buf, int buf_count) {
        return ::readv(fd, buf, buf_count);
    }

    int Accept(int fd, struct sockaddr_in& addr) {
        socklen_t len = sizeof addr;
        int conn_fd = ::accept(fd, sockaddr_cast(&addr), &len);
        if (conn_fd < 0) {
            LOG_ERROR("socket accept failed, errorcode:%d.", errno);
            return conn_fd;
        }
        
        // set non-block
        int flags = ::fcntl(conn_fd, F_GETFL, 0);
        flags |= O_NONBLOCK;
        int res = ::fcntl(conn_fd, F_SETFL, flags);
        if (res < 0) {
            LOG_ERROR("set conn_fd socket non-block error, errorcode:%d.", errno);
        }

        // set close on exec
        flags = ::fcntl(conn_fd, F_GETFD, 0);
        flags |= FD_CLOEXEC;
        res = ::fcntl(conn_fd, F_SETFD, flags);
        if (res < 0) {
            LOG_ERROR("set conn_fd socket close on exec error, errorcode:%d.", errno);
        }
        return conn_fd;
    }

    int Connect(int fd, const struct sockaddr_in& addr) {
        return ::connect(fd, sockaddr_cast(&addr), sizeof addr);
    }

    struct sockaddr_in GetHostAddr(int sockfd) {
        struct sockaddr_in host_addr;
        memset(&host_addr, 0x00, sizeof host_addr);
        socklen_t addr_len = sizeof(host_addr);
        if (::getsockname(sockfd, (sockaddr*)&host_addr, &addr_len) < 0) {
            LOG_ERROR("GetHostAddr | getsockname failed.");
        }
        return host_addr;
    }

    struct sockaddr_in GetPeerAddr(int sockfd) {
        struct sockaddr_in peer_addr;
        memset(&peer_addr, 0x00, sizeof peer_addr);
        socklen_t addr_len = sizeof(peer_addr);
        if (::getpeername(sockfd, (sockaddr*)&peer_addr, &addr_len) < 0) {
            LOG_ERROR("GetPeerAddr | getpeername failed.");
        }
        return peer_addr;
    }

    void ToHostPortFormat(char* buf, size_t buf_len, const struct sockaddr_in& addr) {
        char host[INET_ADDRSTRLEN] = "INVALID";
        ::inet_ntop(AF_INET, &addr.sin_addr, host, sizeof host);
        uint16_t port = htons(addr.sin_port);
        snprintf(buf, buf_len, "%s:%u", host, port);
    }

    int GetSockErr(int fd) {
        int optval;
        socklen_t optlen = sizeof optval;
        if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &optval, &optlen) < 0)
            return errno;
        else
            return optval;
    }

    int32_t N2H_32(int32_t be) {
        return ntohl(be);
    }

    int32_t H2N_32(int32_t le) {
        return htonl(le);
    }

    void SetReuse(bool enable, int fd) {
        int optval = enable ? 1 : 0;
        int ret = ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
        if (ret < 0) {
            LOG_ERROR("set reuseaddr failed. errorcode:%d.", errno);
        }
    }

    void ShutDownWrite(int fd) {
        if (::shutdown(fd, SHUT_WR) < 0) {
            LOG_ERROR("shutdown write failed. errorcode:%d.", errno);
        }
    }
    
} // namesapce net
} // namespace dsth


